#include<linux/module.h>
#include<linux/types.h>
#include<linux/fs.h>
#include<linux/errno.h>
#include<linux/mm.h>
#include<linux/sched.h>
#include<linux/init.h>
#include<linux/cdev.h>
#include<asm/io.h>
#include<asm/system.h>
#include<asm/uaccess.h>
//#include<linux/slab.h>
#define VIRTUALDISK_SIZE 0x2000
#define MEM_CLEAR 0x1
#define PORT1_SET 0x2
#define PORT2_SET 0x3
#define VIRTUALDISK_MAJOR 200  //预设主设备号
#define DEVICE_NAME "virtualdisk device"
/*************************************************************/
static int VirtualDisk_major = VIRTUALDISK_MAJOR;
struct VirtualDisk
{
    struct cdev cdev;
    unsigned char mem[VIRTUALDISK_SIZE];
    int port1;
    long port2;
    long count;
};
struct VirtualDisk *VirtualDisk_devp;

/*文件打开函数*/
static int VirtualDisk_open(struct inode *inode, struct file *filp)
{
    filp->private_data =VirtualDisk_devp;  //内核层的结构指针赋值给用户层
    struct VirtualDisk *devp =(struct VirtualDisk *)filp->private_data;
    devp->count++;
    return 0;
}
/*文件释放*/
static int VirtualDisk_release(struct inode *inode, struct file *filp)
{
    struct VirtualDisk *devp = (struct VirtualDisk *)filp->private_data; //操作文件结构指针里面的计数count-1
    devp->count--;
    return 0;
}
/*读函数*/
static ssize_t VirtualDisk_read(struct file *filp, char __user *buf, size_t size, loff_t *ppos)
{
    unsigned long p = *ppos;    //记录文件指针偏移位置
    unsigned int count = size;  //记录读出的字节数
    int ret = 0;
    struct VirtualDisk *devp = filp->private_data;
    /*分析和获取有效的读长度size*/
    if (p>=VIRTUALDISK_SIZE)
        return count?-ENXIO:0;
    if (count > VIRTUALDISK_SIZE - p)
        count = VIRTUALDISK_SIZE - p;
    /*数据从内核空间copy->用户空间*/
    if(copy_to_user(buf, (char *)(devp->mem +p), count))
    {
        ret = - EFAULT;
    }
    else
    {
        *ppos += count;
        ret = count;
        printk(KERN_INFO"read %d bytes(s) from %ld\n", count, p);
    }
    return ret;
}
/*写函数*/
static ssize_t VirtualDisk_write(struct file *filp, const char  *buf, size_t size, loff_t *ppos)
{
    unsigned long p = *ppos;    //记录文件指针偏移位置
    unsigned int count = size;  //记录写入的字节数
    int ret = 0;
    struct VirtualDisk *devp = filp->private_data;
    if (p>=VIRTUALDISK_SIZE)
        return count?-ENXIO:0;
    if (count > VIRTUALDISK_SIZE - p)
        count = VIRTUALDISK_SIZE - p;
    /*数据从用户空间->内核空间*/
    if(copy_from_user(devp->mem +p, buf, count))
    {
        ret = - EFAULT;
    }
    else
    {
        *ppos += count;
        ret = count;
        printk(KERN_INFO"write %d bytes(s) from %ld\n", count, p);
    }
    return ret;    
}
/*seek文件定位函数*/
static loff_t VirtualDisk_llseek(struct file *filp, loff_t offset, int orig)
{
    loff_t ret = 0;
    switch (orig)
    {
        case SEEK_SET:
            if(offset < 0)
            {
                ret = - EINVAL;
                break;
            }
            if((unsigned int)offset > VIRTUALDISK_SIZE)
            {
                ret = -EINVAL;
                break;
            }
            filp->f_pos = (unsigned int)offset;
            ret = filp->f_pos;
            break;
        case SEEK_CUR:
            if(filp->f_pos + offset < 0)
            {
                ret = - EINVAL;
                break;
            }
            if(filp->f_pos + offset > VIRTUALDISK_SIZE)
            {
                ret = -EINVAL;
                break;
            }
            filp->f_pos = filp->f_pos + offset;
            ret = filp->f_pos;
            break;
        default:
            ret = -EINVAL;
            break;
    }
    return ret;
}
/*ioctl设备控制函数*/
static long VirtualDisk_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
    struct VirtualDisk *devp = (struct VirtualDisk *)filp->private_data;
    switch (cmd)
    {
        case MEM_CLEAR:
            memset(devp->mem, 0, VIRTUALDISK_SIZE);
            printk(KERN_INFO"VirtualDisk is set to zero\n");
            break;         
        case PORT1_SET:
            devp->port1 = 0;
            break;
        case PORT2_SET:
            devp->port2 = 0;
            break;
      //  dafault:
            return -EINVAL;
    }
    return 0;

}


/*文件操作结构体*/
static const struct file_operations VirtualDisk_fops = 
{
    .owner =     THIS_MODULE,
    .open = 	VirtualDisk_open,
    .read = 	VirtualDisk_read,
    .write = 	VirtualDisk_write,
    .llseek = 	VirtualDisk_llseek,
    .release =	VirtualDisk_release,
    .compat_ioctl =    VirtualDisk_ioctl,
};
/*初始化并且注册cdev*/
static void VirtualDisk_setup_cdev(struct VirtualDisk *devp, int minor)
{
    int err;
    dev_t devno = MKDEV(VirtualDisk_major, minor);
    cdev_init(&(devp->cdev),&VirtualDisk_fops);
    devp->cdev.owner = THIS_MODULE;
    devp->cdev.ops = &VirtualDisk_fops;
    err = cdev_add(&(devp->cdev), devno, 1);  //把cdev注册到内核
    if(err)
        printk(KERN_NOTICE"Error in cdev_add()\n");
}

/*设备加载*/
static int __init VirtualDisk_init(void)
{
    int result;
    dev_t devno = MKDEV(VirtualDisk_major, 0);
    if (VirtualDisk_major)          //申请设备号
        result = register_chrdev_region(devno, 1, DEVICE_NAME);
    else
    {
        result = alloc_chrdev_region(&devno, 0, 1, DEVICE_NAME);
        VirtualDisk_major = MAJOR(devno);
    }
    if (result<0)
    return result;
    
    VirtualDisk_devp = (struct VirtualDisk *)kmalloc(sizeof(struct VirtualDisk), GFP_KERNEL);
    if(!VirtualDisk_devp)
    {
        result = -ENOMEM;
        goto fail_kmalloc;
    }
    memset(VirtualDisk_devp, 0, sizeof(struct VirtualDisk));
    VirtualDisk_setup_cdev(VirtualDisk_devp, 0);  //初始化并且添加cdev结构体
    return 0;
    fail_kmalloc:
        unregister_chrdev_region(devno, 1);   //销毁设备号
    return result;
}
/*模块卸载*/
static void __exit VirtualDisk_exit(void)
{
    cdev_del(&VirtualDisk_devp->cdev);  //注销cdev
    kfree(VirtualDisk_devp);    //释放kmalloc申请的用于存放自定义设备结构体的内存空间
    unregister_chrdev_region(MKDEV(VirtualDisk_major, 0), 1); 
}

module_init(VirtualDisk_init);
module_exit(VirtualDisk_exit);
MODULE_LICENSE("Dual BSD/GPL");